//
// Created by haoy on 2017/3/17.
//

#include "rtp_analyzer.hpp"

#include "rtp_reader.hpp"
#include "h264/h264decoder.h"
#include "h264/h264depacketizer.h"
#include "vp8/vp8depacketizer.h"

#include <h264/h264_nal.hpp>

#define PP_DEBUG(event, fmt) LOG4CPLUS_DEBUG("", "analyzer: " << event << fmt)
#define PP_INFO(event, fmt) LOG4CPLUS_INFO("", "analyzer: " << event << fmt)
#define PP_WARN(event, fmt) LOG4CPLUS_WARN("", "analyzer: " << event << fmt)
#define PP_ERROR(event, fmt) LOG4CPLUS_ERROR("", "analyzer: " << event << fmt)

namespace {
mediacodec_id_t codecToMCodec(uint32_t codec) {
    switch (codec) {
        case VideoCodec::VP8:
            return MCODEC_ID_VP8;
        case VideoCodec::H264:
            return MCODEC_ID_H264;
        case AudioCodec::OPUS:
            return MCODEC_ID_OPUS;
        case AudioCodec::AAC:
            return MCODEC_ID_AAC;
    }
    return MCODEC_ID_UNKNOWN;
}
}

class RTPAnalyzerImpl : public RTPAnalyzer {
public:
    RTPAnalyzerImpl() {}
    virtual ~RTPAnalyzerImpl() {}

    virtual void analyzer(const std::string& filepath, const Filters::shared filter) override {
        _filter = filter->getMask();
        _LogLevel = filter->getLogLevel();
        _roll_number = filter->getRollNumber();

        _tlv_reader.input(filepath);
        _tlv_reader.audio(_filter&Filters::AUDIO);
        _tlv_reader.video(_filter&Filters::VIDEO);
        // _tlv_reader.audio(true);
        // _tlv_reader.video(true);
        _tlv_reader.setLogLevel(filter->getLogLevel());
        _tlv_reader.setRollNumber(_roll_number);
        _tlv_reader.handler(this);
        _tlv_reader.run();
    }

private:
    virtual void onEnd() {
        PP_DEBUG("Analyzer complete :", FMT());
        PP_DEBUG("\tVideo :", FMT().kv("Pack Num", _vpkt_count)
                                    .kv("Lost Pack Num", _vlost_pkt_count)
                                    .kv("Lost Pack Rate", _vlost_pkt_count * 1.0 / (_vpkt_count + _vlost_pkt_count)));
        PP_DEBUG("\tAudio :", FMT().kv("Pack Num", _apkt_count)
                                    .kv("Lost Pack Num", _alost_pkt_count)
                                    .kv("Lost Pack Rate", _alost_pkt_count * 1.0 / (_apkt_count + _alost_pkt_count))
                                    .kv("Silence Pack Num", _silence_pkt_count));
    }

    virtual void onCodecRegistry(CodecRegistry& codecs, uint8_t pt) override {
        _codecs = &codecs;
        if(_filter&Filters::Mask::CODEC) {
            PP_DEBUG("Add Media Codec :", FMT().kv("MediaType", MediaFrame::TypeToString(codecs.getMediaTypeForPT(pt)))
                                                .kv("CodecName", codecs.getCodecNameForPT(pt))
                                                .kv("PayloadType", (int)pt)
                                                .kv("Channels", codecs.getChannelsForPT(pt))
                                                .kv("SampleRate", codecs.getSampleRateForPT(pt)));
        }
    }

    virtual void onAudioPacket(RTPPacket* packet, uint64_t pts) override {
        if(_has_audio) {
            if (_audio_codec == MCODEC_ID_UNKNOWN) {
                _audio_codec = codecToMCodec(packet->GetCodec());
            } else if (_audio_codec != codecToMCodec(packet->GetCodec())) {
                PP_ERROR("found more than one audio codec\n", "");
                exit(-1);
            }

            _audio_sample_rate = _codecs->getSampleRateForPT(packet->GetPayloadType());
            _audio_channels = _codecs->getChannelsForPT(packet->GetPayloadType());
        }

        _has_audio = true;

        WORD seq_num = packet->GetSeqNum();
        if(!_alast_pkt_ts) {
            _alast_pkt_ts = packet->GetTimestamp();
            _alast_pkt_gts = packet->GetTime();
        }

        int duration = 20;
        int samples = 20.0 * packet->GetClockRate() / 1000;
        int ts_diff = packet->GetTimestamp() - _alast_pkt_ts;
        if(samples <= 0 || ts_diff / samples > 1 || 1 == packet->GetMediaLength()) {
            ++_silence_pkt_count;
        }
        if(_LogLevel&Filters::LogLv::FRAME && _apkt_count + _vpkt_count <= _roll_number) {
            PP_DEBUG("Audio", FMT().kv("sn", seq_num)
                                    .v(AudioCodec::GetNameFor((AudioCodec::Type)packet->GetCodec()))
                                    .kv("pt", packet->GetPayloadType())
                                    .kv("len", packet->GetMediaLength())
                                    .kv("m", (int)packet->GetMark())
                                    .kv("ts", packet->GetTimestamp())
                                    .kv("t-diff", packet->GetTimestamp() - _alast_pkt_ts)
                                    .kv("gts", packet->GetTime())
                                    .kv("g-diff", (DWORD)(packet->GetTime() - _alast_pkt_gts)));
        }

        _alast_pkt_ts = packet->GetTimestamp();
        _alast_pkt_gts = packet->GetTime();
        if(0 == _alast_pkt_num) {
            _alast_pkt_num = seq_num;
        }else if(seq_num != _alast_pkt_num + 1){
            WORD lost_num_delta = (seq_num > _alast_pkt_num + 1) ? (seq_num - _alast_pkt_num - 1): 0;
            _alost_pkt_count += lost_num_delta;
        }
        ++_apkt_count;
        _alast_pkt_num = seq_num;
    }

    virtual void onVideoPacket(RTPPacket* packet, uint64_t pts) override {
        if (packet->GetCodec() != VideoCodec::VP8 && packet->GetCodec() != VideoCodec::H264) {
            dbgd("packet %d ignored: codec %s not supported", packet->GetExtSeqNum(),
                VideoCodec::GetNameFor(VideoCodec::Type(packet->GetCodec())));
            return;
        }

        if (_video_depacketizer == nullptr) {
            switch (packet->GetCodec()) {
                case VideoCodec::VP8:
                    _video_depacketizer = new VP8Depacketizer();
                    break;
                case VideoCodec::H264:
                    _video_depacketizer = new H264Depacketizer();
                    break;
            }
        }



        int keyframe = -1;
        int picture_id = -1;
        if(packet->GetCodec() == VideoCodec::VP8) {
            //get vp8 frame type
            {
                const uint8_t* buf = packet->GetMediaData();
                // bool direct = false;    //是否直接扔掉后续的帧，直到下一个关键帧到来
                int offset = 0;
                bool x = buf[offset] & 0x80;			//X:1
                bool s = buf[offset] & 0x10;			//S:1
                uint8_t pid = buf[offset] & 0x07;       //pid:3
                // printf("|sn=%5d|m=%d|x=%d|s=%d|pid=%d|", packet->GetSeqNum(), packet->GetMark(), x, s,pid);
                offset += 1;
                if(x) {
                    bool i		= buf[offset] & 0x80;		//I:1
                    bool l		= buf[offset] & 0x40;		//L:1
                    bool t		= buf[offset] & 0x20;		//T:1
                    // printf("i=%d|l=%d|t=%d|", i, l, t);
                    // bool k		= buf[offset] & 0x10;		//K:1
                    offset += 1;
                    if(i) {
                        bool m = buf[offset] & 0x80;				//M
                        // short picture_id = 0;
                        if(m) {
                            picture_id = (uint16_t)(buf[offset] & 0x7f) << 8 | \
                                (uint16_t)buf[offset + 1];			//PictureID     15 bytes
                            offset += 1;
                        } 
                        else {
                            picture_id = buf[offset] & 0x70;		//PictureID 
                        }
                        // printf("m=%d|picID=%5d|", m, picture_id);
                        offset += 1;
                    }
                    if(l) offset += 1;
                    if(t) offset += 1;
                } 
                if(s && !pid) {
                    keyframe = (buf[offset] & 0x01) == 0;
                    // printf("p=%d|", p);
                }
            }
        }


        // this check is disabled due to fec presence:
        // when input:
        // 1 vp8 2 fec 3 vp8
        // the seq is not continuous
        // the check in depacketizer (packet->timestamp() != last_timestamp) has same effect
    //    if (packet->GetExtSeqNum() != _next_video_extseq && _next_video_extseq != (uint32_t) -1) {
    //        PP_DEBUG("reset frame: sequence not continous",
    //                 FMT().kv("cur", packet->GetExtSeqNum()).kv("next", _next_video_extseq));
    //        _video_depacketizer->ResetFrame();
    //    }
    //    _next_video_extseq = packet->GetExtSeqNum() + 1;


    //    if (packet->GetCodec() == VideoCodec::VP8) {
    //        uint8_t *buf = packet->GetMediaData();
    //        if (buf[0] >> 4 & 0x1) {
    //            _video_depacketizer->ResetFrame();
    //        }
    //    }

    //    if (packet->GetMediaLength() > 16) {
    //        static char hex_buf[64];
    //        for (int i = 0; i < 16; i++) {
    //            sprintf(hex_buf+3*i, "%02X ", packet->GetMediaData()[i]);
    //        }
    //        hex_buf[48] = '\0';
    //        PP_WARN("packet",FMT().kv("buf", std::string(hex_buf)));
    //    }

        MediaFrame *frame = _video_depacketizer->AddPacket(packet);

        if(frame && frame->GetLength() != 0) {



            // _has_video = true;
            if (_video_codec == MCODEC_ID_UNKNOWN) {
                _video_codec = codecToMCodec(packet->GetCodec());
            } else if (_video_codec != codecToMCodec(packet->GetCodec())) {
                PP_ERROR("found more than one video codec\n", "");
                exit(-1);
            }

            // 2. decode frame
            // packet has same codec to frame
            switch (packet->GetCodec()) {
                case VideoCodec::VP8: {
                    if (!_vpx_codec_inited) {
                        vpx_codec_err_t err = vpx_codec_dec_init(&_vpx_codec_ctx, vpx_codec_vp8_dx(), NULL, 0);
                        if (err != VPX_CODEC_OK) {
                            PP_ERROR("vpx_codec_dec_init error",
                                    FMT().kv("err", err).kv("cause", vpx_codec_err_to_string(err)));
                            exit(-1);
                        } else {
                            _vpx_codec_inited = true;
                        }
                    }
        //            if (frame->GetLength() > 10) {
        //                static char hex_buf[64];
        //                for (int i = 0; i < 10; i++) {
        //                    sprintf(hex_buf+2*i, "%02X ", frame->GetData()[i]);
        //                }
        //                hex_buf[30] = '\0';
        //                PP_WARN("frame",FMT().kv("buf", std::string(hex_buf)));
        //            }

                    vpx_codec_err_t err = vpx_codec_decode(&_vpx_codec_ctx, frame->GetData(), frame->GetLength(), NULL, 0);
                    if (err != VPX_CODEC_OK) {
                        PP_ERROR("vpx_codec_decode err", FMT().kv("err", err).kv("cause", vpx_codec_err_to_string(err)));
                    } else {
                        vpx_codec_iter_t iter = NULL;
                        vpx_image_t *img = vpx_codec_get_frame(&_vpx_codec_ctx, &iter);
                        if (!img) {
                            PP_DEBUG("no image", "");
                        }

                        //Get dimensions
                        uint32_t width = img->d_w;
                        uint32_t height = img->d_h;
                        _video_width = std::max(_video_width, width);
                        _video_height = std::max(_video_height, height);
                    }
                    break;
                }
                case VideoCodec::H264:
                    if (!_video_decoder) {
                        _video_decoder = new H264Decoder();
                    }
                    //dbgd("pre-proc: decoding following bytes:\n%s", hexify(frame->GetData(), frame->GetLength()).c_str());
                    int decoded = _video_decoder->Decode(frame->GetData(), frame->GetLength());
                    int width = _video_decoder->GetWidth();
                    int height = _video_decoder->GetHeight();
                    keyframe = _video_decoder->IsKeyFrame();
                    if (decoded == 1 
                    && (_video_width != width
                    || _video_height != height)) {
                        // dbgd("pre-proc: decoded h264 %s frame [%dx%d]", (_video_decoder->IsKeyFrame() ? "I" : "non-I"), width,
                        //     height);  
                        _video_width = std::max(_video_width, (uint32_t) width);
                        _video_height = std::max(_video_height, (uint32_t) height);
                    }
            }
        }



        WORD seq_num = packet->GetSeqNum();
        if(!_vlast_pkt_ts) {
            _vlast_pkt_ts = packet->GetTimestamp();
            _vlast_pkt_gts = packet->GetTime();
        }
        if(_LogLevel&Filters::LogLv::FRAME && _apkt_count + _vpkt_count <= _roll_number) {
            PP_DEBUG("Video ", FMT().kv("sn", seq_num)
                                        .v(VideoCodec::GetNameFor((VideoCodec::Type)packet->GetCodec()))
                                        .kv("k", keyframe)
                                        .kv("pid", picture_id)
                                        .kv("pt", packet->GetPayloadType())
                                        .kv("len", packet->GetMediaLength())
                                        .kv("m", (int)packet->GetMark())
                                        .kv("ts", packet->GetTimestamp())
                                        .kv("t-diff", packet->GetTimestamp() - _vlast_pkt_ts)
                                        .kv("gts", packet->GetTime())
                                        .kv("g-diff", (DWORD)(packet->GetTime() - _vlast_pkt_gts)));
        }
        _vlast_pkt_ts = packet->GetTimestamp();
        _vlast_pkt_gts = packet->GetTime();
        if(0 == _vlast_pkt_num) {
            _vlast_pkt_num = seq_num;
        }
        
        if(seq_num != _vlast_pkt_num + 1){
            WORD lost_num_delta = (seq_num > _vlast_pkt_num + 1) ? (seq_num - _vlast_pkt_num - 1): 0;
            _vlost_pkt_count += lost_num_delta;
        }
        ++_vpkt_count;
        _vlast_pkt_num = seq_num;
    }

private:
    RTPReader _tlv_reader;
    Filters::MaskType _filter = Filters::Mask::NONE;
    Filters::LogLevel _LogLevel = Filters::LogLv::ALL_LOG;
    int _roll_number = 0;
    uint32_t _vlast_pkt_num = 0;
    uint32_t _vpkt_count = 0;
    uint32_t _vlost_pkt_count = 0;
    uint32_t _vlast_pkt_ts = 0;
    uint32_t _vlast_pkt_gts = 0;

    uint32_t _alast_pkt_num = 0;
    uint32_t _apkt_count = 0;
    uint32_t _alost_pkt_count = 0;
    uint32_t _silence_pkt_count = 0;
    uint32_t _alast_pkt_ts = 0;
    uint32_t _alast_pkt_gts = 0;

    RTPDepacketizer* _video_depacketizer = NULL;
    CodecRegistry* _codecs;

    bool _has_video = false;
    mediacodec_id_t _video_codec = MCODEC_ID_UNKNOWN;
    uint32_t _video_width = 0;
    uint32_t _video_height = 0;
    uint32_t _video_fps = 0;

    bool _has_audio = false;
    mediacodec_id_t _audio_codec = MCODEC_ID_UNKNOWN;
    uint32_t _audio_sample_rate = 0;
    uint32_t _audio_channels = 0;

    vpx_codec_ctx_t _vpx_codec_ctx;
    bool _vpx_codec_inited = false;

    // uint32_t _next_video_extseq = (uint32_t)-1;
    // currently, only h.264 use this decoder
    VideoDecoder* _video_decoder = nullptr;
};

RTPAnalyzer::shared RTPAnalyzer::Create() {
    return std::make_shared<RTPAnalyzerImpl>();
}

// #include <iomanip>
// #include <sstream>
// static std::string hexify(uint8_t* buf, int len) {
//     std::ostringstream w;
//     for (int i = 0; i < len; ++i) {
//         if (i % 16 == 0) {
//             w << std::setw(8) << std::setfill('0') << i << ": ";
//         }
//         w << std::setw(2) << std::setfill('0') << std::hex << int(buf[i]) << " ";

//         if (i % 16 == 7) {
//             w << "    ";
//         }
//         if (i % 16 == 15) {
//             w << std::endl;
//         }
//     }
//     return w.str();
// }
